﻿using ExpressesClass.BestApi.Utils.jsonUtil.iterator;
using System;

namespace ExpressesClass.BestApi.Utils.jsonUtil
{
    class JSONValidator
    {

        private JSONErrorListener listener;
        private StringCharacterIterator it;
        private char c;
        private int col;

        public JSONValidator(JSONErrorListener listener)
        {
            this.listener = listener;
        }

        public Boolean validate(string input)
        {
            input = input.Trim();
            listener.start(input);
            Boolean ret = valid(input);
            listener.end();
            return ret;
        }

        private Boolean valid(String input)
        {
            if ("".Equals(input)) return true;

            Boolean ret = true;
            it = new StringCharacterIterator(input, 0, input.Length, 0);
            c = it.first();
            col = 1;
            if (!value())
            {
                ret = error("value", 1);
            }
            else
            {
                skipWhiteSpace();
                if (c != StringCharacterIterator.DONE)
                {
                    ret = error("end", col);
                }
            }

            return ret;
        }

        private Boolean value()
        {
            return
                literal("true") ||
                literal("false") ||
                literal("null") ||
                stringHan() ||
                numberHan() ||
                objectHan() ||
                arrayHan();
        }

        private Boolean arrayHan()
        {
            return aggregate('[', ']', false);
        }

        private Boolean objectHan()
        {
            return aggregate('{', '}', true);
        }
    
        private Boolean aggregate(char entryCharacter, char exitCharacter, Boolean prefix)
        {
            if (c != entryCharacter) return false;
            nextCharacter();
            skipWhiteSpace();
            if (c == exitCharacter)
            {
                nextCharacter();
                return true;
            }

            for (;;)
            {
                if (prefix)
                {
                    int start = col;
                    if (!stringHan()) return error("string", start);
                    skipWhiteSpace();
                    if (c != ':') return error("colon", col);
                    nextCharacter();
                    skipWhiteSpace();
                }
                if (value())
                {
                    skipWhiteSpace();
                    if (c == ',')
                    {
                        nextCharacter();
                    }
                    else if (c == exitCharacter)
                    {
                        break;
                    }
                    else
                    {
                        return error("comma or " + exitCharacter, col);
                    }
                }
                else
                {
                    return error("value", col);
                }
                skipWhiteSpace();
            }

            nextCharacter();
            return true;
        }

        private Boolean stringHan()
        {
            if (c != '"') return false;
            
            int start = col;
            Boolean escaped = false;

            for (nextCharacter(); c != StringCharacterIterator.DONE; nextCharacter()) {
                if (!escaped && c == '\\') {
                    escaped = true;
                } else if (escaped) {
                    if (!escape()) {
                        return false;
                    }
                escaped = false;
                } else if (c == '"') {
                    nextCharacter();
                    return true;
                }
            }

            return error("quoted string", start);
        }

        private Boolean numberHan()
        {
            if (!char.IsDigit(c) && c != '-') return false;
            int start = col;

            if (c == '-') nextCharacter();

            if (c == '0')
            {
                nextCharacter();
            }
            else if (char.IsDigit(c))
            {
                while (char.IsDigit(c)) nextCharacter();
            }
            else
            {
                return error("number", start);
            }

            if (c == '.')
            {
                nextCharacter();
                if (char.IsDigit(c))
                {
                    while (char.IsDigit(c)) nextCharacter();
                }
                else
                {
                    return error("number", start);
                }
            }

            if (c == 'e' || c == 'E')
            {
                nextCharacter();
                if (c == '+' || c == '-')
                {
                    nextCharacter();
                }
                if (char.IsDigit(c))
                {
                    while (char.IsDigit(c)) nextCharacter();
                }
                else
                {
                    return error("number", start);
                }
            }

            return true;
        }

        private Boolean escape()
        {
            int start = col - 1;
            if ("\\\"/bfnrtu".IndexOf(c) < 0)
            {
                return error("escape sequence \\\",\\\\,\\/,\\b,\\f,\\n,\\r,\\t or \\uxxxx", start);
            }
            if (c == 'u')
            {
                if (!ishex(nextCharacter()) || !ishex(nextCharacter()) ||
                        !ishex(nextCharacter()) || !ishex(nextCharacter()))
                {
                    return error("unicode escape sequence \\uxxxx", start);
                }
            }
            return true;
        }

        private Boolean literal(string text)
        {
            StringCharacterIterator ci = new StringCharacterIterator(text, 0, text.Length, 0);
            char t = ci.first();
            if (c != t) return false;

            int start = col;
            Boolean ret = true;
            for (t = ci.next(); t != StringCharacterIterator.DONE; t = ci.next())
            {
                if (t != nextCharacter())
                {
                    ret = false;
                    break;
                }
            }
            nextCharacter();

            if (!ret) error("literal " + text, start);
            return ret;
        }


        private char nextCharacter()
        {
            c = it.next();
            ++col;
            return c;
        }

        private void skipWhiteSpace()
        {
            while (string.IsNullOrWhiteSpace(Convert.ToString(c)))
            {
                nextCharacter();
            }
        }

        private Boolean error(string type, int col)
        {
            if (listener != null) listener.error(type, col);
            return false;
        }

        private Boolean ishex(char d)
        {
            return "0123456789abcdefABCDEF".IndexOf(c) >= 0;
        }
    }
}
